/*
 * Copyright (c) 2023-2024 elsfs Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.elsfs.cloud.module.datasource.biz.manager;

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.sql.DataSource;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.tools.generic.DateTool;
import org.apache.velocity.tools.generic.MathTool;
import org.elsfs.cloud.common.core.utils.SpringContextHolder;
import org.elsfs.cloud.common.util.lang.CollectionUtils;
import org.elsfs.cloud.common.util.lang.NamingCase;
import org.elsfs.cloud.common.util.lang.StringUtils;
import org.elsfs.cloud.module.datasource.biz.entity.GenTableColumn;
import org.elsfs.cloud.module.datasource.biz.entity.GenTemplate;
import org.elsfs.cloud.module.datasource.biz.properties.CodeGenProperties;
import org.elsfs.cloud.module.datasource.biz.repository.GenFieldTypeRepository;
import org.elsfs.cloud.module.datasource.biz.repository.GenGroupRepository;
import org.elsfs.cloud.module.datasource.biz.repository.GenTableColumnRepository;
import org.elsfs.cloud.module.datasource.biz.repository.GenTableRepository;
import org.elsfs.cloud.module.datasource.biz.vo.GenTableVo;
import org.elsfs.cloud.module.datasource.biz.vo.GroupVo;
import org.elsfs.cloud.screw.engine.EngineFileType;
import org.elsfs.cloud.screw.spring.Screw;
import org.elsfs.cloud.screw.spring.ScrewProperties;
import org.springframework.stereotype.Service;

/**
 * 生成表文档
 *
 * @author zeng
 */
@Service
@RequiredArgsConstructor
public class GeneratorDocManager {
  private final Screw screw;
  private final ScrewProperties screwProperties;
  private final CodeGenProperties codeGenProperties;
  private final GenTableColumnRepository genTableColumnRepository;
  private final GenFieldTypeRepository genFieldTypeRepository;
  private final GenTableRepository genTableRepository;
  private final GenGroupRepository genGroupRepository;

  /**
   * 查询数据源对应的文档
   *
   * @param dsName 数据源名称
   */
  @SneakyThrows
  public ByteArrayOutputStream generatorDoc(String dsName) {
    // 设置指定的数据源
    DynamicRoutingDataSource dynamicRoutingDataSource =
        SpringContextHolder.getBean(DynamicRoutingDataSource.class);
    DynamicDataSourceContextHolder.push(dsName);
    DataSource dataSource = dynamicRoutingDataSource.determineDataSource();
    // 生成
    return screw.documentGeneration(dsName, dataSource, screwProperties);
  }

  /**
   * 查询数据源对应的文档
   *
   * @param dsName 数据源名称
   */
  @SneakyThrows
  public ByteArrayOutputStream generatorDoc(String dsName, EngineFileType engineFileType) {
    // 设置指定的数据源
    DynamicRoutingDataSource dynamicRoutingDataSource =
        SpringContextHolder.getBean(DynamicRoutingDataSource.class);
    DynamicDataSourceContextHolder.push(dsName);
    DataSource dataSource = dynamicRoutingDataSource.determineDataSource();
    ScrewProperties properties = new ScrewProperties();
    BeanUtils.copyProperties(screwProperties, properties);
    properties.setFileType(engineFileType);
    // 生成
    return screw.documentGeneration(dsName, dataSource, properties);
  }

  /**
   * 生成代码zip写出
   *
   * @param tableId 表
   * @param zip 输出流
   */
  @SneakyThrows
  public void downloadCode(String tableId, ZipOutputStream zip) {
    // 数据模型
    Map<String, Object> dataModel = getDataModel(tableId);
    String style = (String) dataModel.get("style");
    GroupVo groupVo = genGroupRepository.getGroupVoById(style);
    List<GenTemplate> templateList = groupVo.getTemplateList();
    for (GenTemplate template : templateList) {
      dataModel.put("frontendPath", codeGenProperties.getFrontendPath());
      dataModel.put("backendPath", codeGenProperties.getBackendPath());
      String content = renderStr(template.getTemplateCode(), dataModel);
      String path = renderStr(template.getGeneratorPath(), dataModel);
      // 添加到zip
      zip.putNextEntry(new ZipEntry(path));
      zip.write(content.getBytes(StandardCharsets.UTF_8));
      zip.flush();
      zip.closeEntry();
    }
  }

  /**
   * 预览代码
   *
   * @param tableId 表
   * @return [{模板名称:渲染结果}] ⚠️对应的 map 不支持插入数据
   */
  public List<Map<String, String>> preview(String tableId) {
    // 数据模型
    Map<String, Object> dataModel = getDataModel(tableId);

    // 获取模板列表
    List<GenTemplate> templateList =
        genGroupRepository.getGroupVoById(dataModel.get("style").toString()).getTemplateList();
    // map 简化代码
    return templateList.stream()
        .map(
            template -> {
              // 预览模式下, 使用相对路径展示
              dataModel.put("frontendPath", codeGenProperties.getFrontendPath());
              dataModel.put("backendPath", codeGenProperties.getBackendPath());
              String content = renderStr(template.getTemplateCode(), dataModel);
              String path = renderStr(template.getGeneratorPath(), dataModel);

              // 使用 map 简化代码
              return Map.of("code", content, "codePath", path);
            })
        .collect(Collectors.toList());
  }

  /**
   * 目标目录写入渲染结果
   *
   * @param tableId 表
   */
  public void generatorCode(String tableId) {
    // 数据模型
    Map<String, Object> dataModel = getDataModel(tableId);

    // 获取模板列表，Lambda 表达式简化代码
    List<GenTemplate> templateList =
        genGroupRepository.getGroupVoById(dataModel.get("style").toString()).getTemplateList();

    templateList.forEach(
        template -> {
          String s = renderStr(template.getTemplateCode(), dataModel);
          String path = renderStr(template.getGeneratorPath(), dataModel);
          try (FileWriter writer = new FileWriter(path)) {
            writer.write(s);
          } catch (IOException e) {
            throw new RuntimeException(e);
          }
          // ignore
        });
  }

  /**
   * 渲染文本
   *
   * @param str 数据
   * @param dataModel 数据
   * @return 渲染后的文本
   */
  private String renderStr(String str, Map<String, Object> dataModel) {
    // 设置velocity资源加载器
    Velocity.init();
    VelocityContext context = new VelocityContext(dataModel);
    // 函数库
    context.put("math", new MathTool());
    context.put("dateTool", new DateTool());
    context.put("dict", new DictTool());
    context.put("str", new NamingCaseTool());
    StringWriter stringWriter = new StringWriter();
    Velocity.evaluate(context, stringWriter, "renderStr", str);
    return stringWriter.toString();
  }

  /**
   * 通过 Lambda 表达式优化的获取数据模型方法
   *
   * @param tableId 表格 ID
   * @return 数据模型 Map 对象
   */
  private Map<String, Object> getDataModel(String tableId) {
    // 获取表格信息
    var table = genTableRepository.getVoByIdVo(tableId);
    // 获取字段列表
    List<GenTableColumn> fieldList =
        genTableColumnRepository
            .lambdaQuery()
            .eq(GenTableColumn::getDsName, table.getDsName())
            .eq(GenTableColumn::getTableName, table.getTableName())
            .orderByAsc(GenTableColumn::getSort)
            .list();

    table.setFieldList(fieldList);

    // 创建数据模型对象
    Map<String, Object> dataModel = new HashMap<>();

    // 填充数据模型
    dataModel.put("dbType", table.getDbType());
    dataModel.put("package", table.getPackageName());
    dataModel.put("packagePath", table.getPackageName().replace(".", File.separator));
    dataModel.put("version", table.getVersion());
    dataModel.put("moduleName", table.getModuleName());
    dataModel.put("ModuleName", StringUtils.upperFirst(table.getModuleName()));
    dataModel.put("functionName", table.getFunctionName());
    dataModel.put("FunctionName", StringUtils.upperFirst(table.getFunctionName()));
    dataModel.put("formLayout", table.getFormLayout());
    dataModel.put("style", table.getStyle().toString());
    dataModel.put("author", table.getAuthor());
    dataModel.put("datetime", LocalDateTime.now());
    dataModel.put("date", LocalDateTime.now().getDayOfMonth());
    setFieldTypeList(dataModel, table);

    // 获取导入的包列表
    Set<String> importList =
        genFieldTypeRepository.getPackageByTableId(table.getDsName(), table.getTableName());
    dataModel.put("importList", importList);
    dataModel.put("tableName", table.getTableName());
    dataModel.put("tableComment", table.getTableComment());
    dataModel.put("className", StringUtils.lowerFirst(table.getClassName()));
    dataModel.put("ClassName", table.getClassName());
    dataModel.put("fieldList", table.getFieldList());

    dataModel.put("backendPath", table.getBackendPath());
    dataModel.put("frontendPath", table.getFrontendPath());
    return dataModel;
  }

  /**
   * 将表字段按照类型分组并存储到数据模型中
   *
   * @param dataModel 存储数据的 Map 对象
   * @param table 表信息对象
   */
  private void setFieldTypeList(Map<String, Object> dataModel, GenTableVo table) {
    // 按字段类型分组，使用 Map 存储不同类型的字段列表
    Map<Boolean, List<GenTableColumn>> typeMap =
        table.getFieldList().stream()
            .collect(
                Collectors.partitioningBy(
                    columnEntity -> BooleanUtils.toBoolean(columnEntity.getPrimaryPk())));

    // 从分组后的 Map 中获取不同类型的字段列表
    List<GenTableColumn> primaryList = typeMap.get(true);
    List<GenTableColumn> formList =
        typeMap.get(false).stream()
            .filter(columnEntity -> BooleanUtils.toBoolean(columnEntity.getFormItem()))
            .collect(Collectors.toList());

    if (CollectionUtils.isNotEmpty(primaryList)) {
      dataModel.put("pk", primaryList.get(0));
    }
    dataModel.put("primaryList", primaryList);
    dataModel.put("formList", formList);
    List<GenTableColumn> gridList =
        typeMap.get(false).stream()
            .filter(columnEntity -> BooleanUtils.toBoolean(columnEntity.getGridItem()))
            .collect(Collectors.toList());
    dataModel.put("gridList", gridList);
    List<GenTableColumn> queryList =
        typeMap.get(false).stream()
            .filter(columnEntity -> BooleanUtils.toBoolean(columnEntity.getQueryItem()))
            .collect(Collectors.toList());
    dataModel.put("queryList", queryList);
  }

  /**
   * 字典工具
   *
   * @author zeng
   */
  public static class DictTool {
    public DictTool() {}

    /**
     * 将字段列表转换为带有双引号的逗号分隔的字符串
     *
     * @return 带有双引号的逗号分隔的字符串
     */
    public static String quotation(List<String> fields) {
      StringBuilder s = new StringBuilder();
      for (String field : fields) {
        if (s.length() > 0) {
          s.append(String.format(",'%s'", field));
        } else {
          s.append(String.format("'%s'", field));
        }
      }
      return s.toString();
    }

    /**
     * 将字段列表转换为逗号分隔的字符串
     *
     * @return 逗号分隔的字符串
     */
    public static String format(List<String> fields) {
      // 将字段列表转换为逗号分隔的字符串
      StringBuilder s = new StringBuilder();
      for (String field : fields) {
        s.append(String.format("%s,", field));
      }
      return s.toString();
    }
  }

  /** 命名规则处理，针对驼峰，下划线等处理 */
  public static class NamingCaseTool {

    /**
     * 传入字段获取的get方法
     *
     * @param in 字段名称
     * @return 方法名称
     */
    public static String getProperty(String in) {
      return String.format("get%s", NamingCase.toPascalCase(in));
    }

    public static String setProperty(String in) {
      return String.format("set%s", NamingCase.toPascalCase(in));
    }

    /**
     * 首字母大写
     *
     * @param in 字段
     * @return 首字母大写
     */
    public static String pascalCase(String in) {
      return String.format(NamingCase.toPascalCase(in));
    }
  }
}
